import os
import uuid
import types
from twisted.internet import defer, reactor, protocol
from twisted.python.failure import Failure
import mediadart
from mediadart.mqueue.mqserver import startMQ
from mediadart import log
from mediadart.config import Configurator

class RunProcError(Exception): 
    pass


class RunProc(protocol.ProcessProtocol):
    """
    Wrapper for calling an external process in a twisted-approved way.

    Parameters:
        exe: name of the executable
        argv: list of arguments
        env: dictionary of the environment settings to add to the existing environment
             if env contains the key "md_overwrite_env" the environment replaces the
             existing.
        cb:  callback(data, *cbargv) to receive the stdout of the process.
        cbargv: additional arguments to the callback
        do_log: verbose logging
    """
    @classmethod
    def run(cls, exe, argv=[], env={}, cb=None, cbargv=[], do_log=1):
        self = cls()
        self.exe = str(getattr(exe, 'exe', exe))
        self.argv = getattr(exe, 'argv', argv)
        if type(self.argv) in [types.UnicodeType, types.StringType]:
            self.argv = self.argv.split()
        self.argv = map(str, self.argv)
        if env.has_key('md_overwrite_env'):
            self.env = {}
        else:
            self.env = os.environ
        self.env.update(getattr(exe, 'env', env))
        self._log = do_log
        if self._log:
            log.debug('starting: %s %s' % (self.exe, ' '.join(self.argv)))
            if self._log > 1:
                log.debug('Environment: %s' % self.env)
        # spawn the process at low priority
        self._proc = reactor.spawnProcess(self, 'nice', ['nice', '-n', '20', 'ionice', '-n', '7', '-t', self.exe] + self.argv, env=self.env)
        if self._proc is None:
            raise RunProcError('failed to launch %s' % self._script.exe )
        else:
            self.pid = str(self._proc.pid) or 'unavailable'
        if self._log > 1:
            log.debug('started proc %s' % (self.pid))
        self._cb = cb     # called with the bytes received
        self._cbargv = cbargv  # additional arguments passed to the callback
        return self

    def __init__(self):
        self.__stdout = ""
        self.__stderr = ""
        self._max_stdout = 8*1024
        self._max_stderr = 8*1024
        self.__requests = []

    def getResult(self):
        """
        Return a Deferred which will fire with the result of L{parseChunks}
        when the child process exits.
        """
        d = defer.Deferred()
        self.__requests.append(d)
        return d

    def _fireResultDeferreds(self, result):
        """
        Callback all Deferreds returned up until now by L{getResult}
        with the given result object.
        """
        requests = self.__requests
        self.__requests = None
        for d in requests:
            d.callback(result)

    def _fireErrorDeferreds(self, result):
        """
        Callback all Deferreds returned up until now by L{getResult}
        with the given result object.
        """
        requests = self.__requests
        self.__requests = None
        for d in requests:
            d.errback(Failure(result))

    def outReceived(self, data):
        """
        Accumulate output from the child process in a list.
        """
        if self._cb:
            self._cb(data, *self._cbargv)
        else:
            self.__stdout = self.__stdout[-self._max_stdout:] + data

    def errReceived(self, data):
        log.error('proc %s(echoing stderr): %s' % (self.pid, data.strip()))
        self.__stderr = self.__stderr[-self._max_stderr:] + data

    def processEnded(self, reason):
        """
        Handle process termination by parsing all received output and firing
        any waiting Deferreds.
        """
        if reason.value.exitCode == 0:
            self._fireResultDeferreds({'exitCode':0, 'data':''.join(self.__stdout)})
        else:
            self._fireErrorDeferreds({'exitCode':reason.value.exitCode, 'data':self.__stderr})


def default_start_mqueue(server_class, args=[]):
    "starts the server <server_class> using information passed as args or in configuration"
    startMQ(server_class, args, **get_mq_params())

def get_mq_params(broker_ip=None, broker_port=None, username=None, password=None, vhost=None):
    ret = {'broker_ip':broker_ip, 'broker_port':broker_port, 'username':username, 'password':password, 'vhost':vhost }
    c = Configurator()
    for key in ret.keys():
        try:
            if not ret[key]:
                ret[key] = c.get('MQUEUE', key)
        except Exception, e:
            log.error('UNABLE TO READ MQUEUE CONFIGURATION: %s' % str(e))
            raise
    return ret


# UTility function
from urlparse import urlsplit

def get_local_url(section, option):
    """This function is used to build a local url from a path and the values of host
       and port on which the local WWW servers are started"""
    c = Configurator()
    try:
        host = c.get('WWW', 'host')
    except Exception, e:
        host = '127.0.0.1'
    port = c.get('WWW', 'port')
    path = c.get(section, option)
    p = urlsplit(path)
    if p.scheme:
        return path   # already an url
    else:
        if path.startswith('/'):
            path = path[1:]
        return 'http://%s:%s/%s' % (host, port, path)

def normpath(path):
    ret = os.path.normpath(path)
    if ret.startswith('//'):
        return ret[1:]
    else:
        return ret

def get_python_path():
    return os.path.dirname(os.path.dirname(mediadart.__file__))

def new_service_id():
    print "_id = '%s'" % uuid.uuid4().get_urn()

if __name__=='__main__':
    new_service_id()

#def main():
#    class S:
#        exe = '/bin/echo'
#        args = 'orlando murru cagliari'
#
#    def cb(data, buffer):
#        buffer.append(data)
#
#    def end(result, buffer):
#        print 'process ends with %s' % result
#        print 'stdout = %s' % buffer
#        reactor.stop()
#
#    buffer = []
#    r = RunProc(S(), stdout_cb = lambda data: cb(data, buffer))
#    d=r.run()
#    d.addCallback(end, buffer)
#
#reactor.callWhenRunning(main)
#reactor.run()
